﻿using System;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleDemo
{
    /// <summary>
    /// C#图解教程 第21章 异步编程
    /// </summary>
    public class MyDownloadString
    {
        // 21.1 什么是异步

        Stopwatch sw = new Stopwatch();

        //public void DoRun()
        //{
        //    const int LargeNumber = 6_000_000;
        //    sw.Start();
        //    int t1 = CountCharacters(1, "https://www.baidu.com");
        //    int t2 = CountCharacters(2, "http://www.illustratedcsharp.com");  //http://www.illustratedcsharp.com   http://k3.chunk.cn:8053/POSReport20/Default.aspx
        //    CountToLargeNumber(1, LargeNumber);
        //    CountToLargeNumber(2, LargeNumber);
        //    CountToLargeNumber(3, LargeNumber);
        //    CountToLargeNumber(4, LargeNumber);
        //    Console.WriteLine($"Chars in https://www.baidu.com           : {t1}");
        //    Console.WriteLine($"Chars in http://www.illustratedcsharp.com: {t2}");

        //    sw.Stop();

        //}

        //private int CountCharacters(int id, string uriString)
        //{
        //    WebClient client = new WebClient();
        //    Console.WriteLine("Starting call {0}            : {1} ms", id, sw.Elapsed.TotalMilliseconds);
        //    string result = client.DownloadString(uriString);
        //    Console.WriteLine("         Call {0} completed  : {1} ms", id, sw.Elapsed.TotalMilliseconds);
        //    return result.Length;
        //}

        private void CountToLargeNumber(int id, int value)
        {
            for (long i = 0; i < value; i++)
                ;
            Console.WriteLine("         End counting {0}    : {1} ms", id, sw.Elapsed.TotalMilliseconds);
        }

        //输出结果
        //Starting call 1            : 0.2158 ms
        //         Call 1 completed  : 119.1578 ms
        //Starting call 2            : 119.2332 ms
        //         Call 2 completed  : 572.9299 ms
        //         End counting 1    : 579.2313 ms
        //         End counting 2    : 585.8842 ms
        //         End counting 3    : 592.0836 ms
        //         End counting 4    : 598.7669 ms
        //Chars in https://www.baidu.com           : 9269
        //Chars in http://www.illustratedcsharp.com: 5164

        // 上面 总结了输出结果，展示了不同任务开始和结束的时间。如上所示，Cal1 1 和 Cal1 2占用了大部分时间。但不管哪次调用，绝大部分时间都浪费在等待网站的响应上。

        //使用 async 输出结果
        //Starting call 1        : 0.9094 ms
        //Starting call 2        : 57.1373 ms
        //     End counting 1    : 65.7801 ms
        //     End counting 2    : 71.7681 ms
        //     End counting 3    : 77.9901 ms
        //     End counting 4    : 84.361 ms
        //     Call 1 completed  : 106.8352 ms
        //Chars in https://www.baidu.com           : 9269
        //     Call 2 completed  : 492.4267 ms
        //Chars in http://www.illustratedcsharp.com: 5164

        //改进
        public void DoRunAsync()  // 调用方法 Calling Method
        {
            //Console.WriteLine();
            //Console.WriteLine("-----------------改进后------------------------------");
            //Console.WriteLine();
            const int LargeNumber = 6_000_000;
            sw.Start();
            Task<int> t1 = CountCharactersAsync(1, "https://www.baidu.com");
            Task<int> t2 = CountCharactersAsync(2, "http://www.illustratedcsharp.com");  //http://www.illustratedcsharp.com   http://k3.chunk.cn:8053/POSReport20/Default.aspx
            CountToLargeNumber(1, LargeNumber);
            CountToLargeNumber(2, LargeNumber);
            CountToLargeNumber(3, LargeNumber);
            CountToLargeNumber(4, LargeNumber);
            Console.WriteLine($"Chars in https://www.baidu.com           : {t1.Result}");
            Console.WriteLine($"Chars in http://www.illustratedcsharp.com: {t2.Result}");
            Console.WriteLine("The End");
        }

        private async Task<int> CountCharactersAsync(int id, string uriString)  // 异步方法 Async Method
        {
            WebClient client = new WebClient();
            Console.WriteLine("Starting call {0}            : {1} ms", id, sw.Elapsed.TotalMilliseconds);
            string result = await client.DownloadStringTaskAsync(new Uri(uriString));                       //await 表达式
            Console.WriteLine("         Call {0} completed  : {1} ms", id, sw.Elapsed.TotalMilliseconds);
            return result.Length;
        }


        //以下是代码的执行顺序和主线程的工作流程：
        //  1. 主线程启动 DoRun 方法：
        //      启动 CountCharactersAsync(1, "http://www.microsoft.com")，执行到 await 时暂停。
        //      启动 CountCharactersAsync(2, "http://www.illustratedcsharp.com")，执行到 await 时暂停。

        //  2. 主线程执行 CountToALargeNumber：
        //      主线程依次执行 CountToALargeNumber(1, LargeNumber) 到 CountToALargeNumber(4, LargeNumber)。
        //      这些方法是同步的，主线程会一直执行它们，直到完成。

        //  3. I/O 操作完成：
        //      当 DownloadStringTaskAsync 的 I/O 操作完成后，主线程会从 await 的位置继续执行 CountCharactersAsync 的剩余部分。

        //  4. 输出结果：
        //      主线程输出 t1.Result 和 t2.Result，即两个网站的内容长度。

        //总结
        //  这段代码的所有操作都是在 主线程 中完成的，没有创建额外的线程。
        //  通过 async/await，主线程可以在等待 I/O 操作完成的同时，继续执行其他任务（如 CountToALargeNumber）。
        //  这种机制依赖于操作系统的异步 I/O 支持和 C# 的异步编程模型，而不是多线程并发。



        // 21.3 什么是异步方法
        // 异步方法的控制流
        // 异步方法的结构包含三个不同的区域



        // 21.8 并行循环
        // 任务并行库（Task Parellel Library)。它是BCL中的一个类库，极大地简化了并行编程。
        // Parallel.For循环 和 Parallel.ForEach循环
        public static void Call3()
        {
            Parallel.For(0, 15, i => Console.WriteLine("The square of {0} is {1}", i, i * i));
        }

        public static void Call4()
        {
            const int maxValues = 50;
            int[] squares = new int[maxValues];
            Parallel.For(0, maxValues, i => squares[i] = i * i);
            for (int i = 0; i < squares.Length; i++)
            {
                Console.Write(squares[i] + " ");
            }
            // 在本例中，即使迭代在执行时可能为并行并且为任意顺序。但是最后结果始终是一个包含前50个平方数的数组——并且按顺序排列。
        }


        // BeginInvoke 和 EndInvoke

        // BeginInvoke 从线程池中获取一个线程并且让引用方法在新的线程中开始运行。
        // BeginInvoke 返回给调用线程一个实现IAsyncResult接口的对象的引用。这个接口引用包含了在线程池线程中运行的异步方法的当前状态，原始线程然后可以继续执行。

        // EndInvoke 方法用来获取由异步方法调用返回的值，并且释放线程使用的资源
        //  它接受一个由BeginInvoke方法返回的IAsyncResult对象的引用，并找到它关联的线程
        //  如果线程池的线程已经退出，EndInvoke做如下的事情
        //      它清理退出线程的状态并且释放其资源
        //      它找到引用方法返回的值并且把它作为返回值
        //  如果当EndInvoke被调用时线程池的线程仍然在运行，调用线程就会停止并等待，直到清理完毕并返回值。因为EndInvoke是为开启的线程进行清理，所以必须确保对每一个BeginInvoke都调用EndInvoke
        //  如果异步方法触发了异常，在调用EndInvoke时会抛出异常



        // wait-until-done 模式
        public static void Call5()
        {
            MyDel del = new MyDel(Sum);
            Console.WriteLine("Before BeginInvoke");
            IAsyncResult iar = del.BeginInvoke(3, 5, null, null); //开抬异步调用
            Console.WriteLine("After BeginInvoke");
            Console.WriteLine("Doing stuff");
            long result = del.EndInvoke(iar); //等待结果并获取结果
            Console.WriteLine("After EndInvoke: {0}", result);

            //输出：
            //Before BeginInvoke
            //After BeginInvoke
            //Doing stuff
            //                Inside Sum
            //After EndInvoke: 8
        }
        static long Sum(int x, int y) //声明异步方法
        {
            Console.WriteLine("                Inside Sum");
            Thread.Sleep(100);
            return x + y;
        }



        // 异步编程中最需弄清的是控制流是如何从方法移动到方法的。 
        public static void BtnClick()
        {
            Console.WriteLine("BtnClick Start");
            Task<int> s = AccessTheWebAsync();
            Console.WriteLine("BtnClick End");
        }

        public static async Task<int> AccessTheWebAsync()
        {
            Console.WriteLine("AccessTheWebAsync 1");
            HttpClient client = new HttpClient();
            Console.WriteLine("AccessTheWebAsync 2");
            Task<string> getStringTask = client.GetStringAsync("https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/");

            Console.WriteLine("AccessTheWebAsync 3");
            DoIndependentWork();

            Console.WriteLine("AccessTheWebAsync 4");
            string urlContents = await getStringTask;

            Console.WriteLine("AccessTheWebAsync 5");
            return urlContents.Length;
        }

        public static void DoIndependentWork()
        {
            Console.WriteLine("DoIndependentWork");
        }
    }
    delegate long MyDel(int first, int second);   //声明委托类型
}
